package com.dean.dynamicexe.service;

import com.dean.entity.DeanResponse;
import com.dean.exception.DeanRuntimeException;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.tools.*;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DeanDynamicCompilationService {
    private final static Logger log = LoggerFactory.getLogger(DeanDynamicCompilationService.class);
    // 全类名
    private String fullClassName;
    // 源码
    private String sourceCode;

    // 存储已经编译的字节码
    private static Map<String,ByteJavaFileObject>  javaFileObjectMap = new ConcurrentHashMap<>();

    // Java编译器
    private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    // 编译过程中的输出信息
    private DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();

    public DeanDynamicCompilationService(String sourceCode){
        this.sourceCode = sourceCode;
        this.fullClassName = getFullClassName(sourceCode);
    }

    public DeanDynamicCompilationService(){
    }

    public String getSourceCode() {
        return sourceCode;
    }

    public String getFullClassName() {
        return fullClassName;
    }

    public void setSourceCode(String sourceCode) {
        this.sourceCode = sourceCode;
        this.fullClassName = getFullClassName(sourceCode);
    }

    // 读取Java文件
    public void setSourceCodeFile(String sourceCodeFile){
        File file = new File(sourceCodeFile);
        if(file.exists()){
            setSourceCode(getSourceCode(file));
        }
    }


    /**
     * 解析源码文件
     * @param sourceCodeFile
     * @return
     */
    public  String getSourceCode(File sourceCodeFile) {
        StringBuilder sb = new StringBuilder();
        try(BufferedReader br = new BufferedReader(new FileReader(sourceCodeFile))){
            String line;
            while ((line = br.readLine())!=null){
                sb.append(line);
            }
        } catch (IOException e) {
            log.error(e.getMessage(),e);
            throw new DeanRuntimeException(996,"文件不存在或者文件类型不匹配");
        }
        return sb.toString();
    }

    /**
     * 通过源码获取 full class name
     * @param sourceCode
     * @return
     */
    public String getFullClassName(String sourceCode){
        String className = "";
        Pattern pattern = Pattern.compile("package\\s+\\S+\\s*;");
        Matcher matcher = pattern.matcher(sourceCode);
        if(matcher.find()){
            className = matcher.group().replaceFirst("package","").replace(";","").trim()+".";
        }
        pattern = Pattern.compile("class\\s+\\S+\\s+(\\{|extends|implements)");
        matcher = pattern.matcher(sourceCode);
        if(matcher.find()){
            className += matcher.group().replace("class","").replace("{","").replace("extends","").replace("implements","").trim();
        }
        return className;
    }

    /**
     * 编译源代码
     * @return
     */
    public boolean compiler(){
        log.debug("java compiler is starting classname is :{} sourcecode is :\r\n{}",this.fullClassName,this.sourceCode);
        // 记录编译开始时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("class {} start compiler,开始时间为:{}",fullClassName,stopWatch.getStartTime());

        // 标准的内容管理器 更换成自己的实现 覆盖部分方法
        StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(diagnosticCollector,null,null);
        JavaFileManager javaFileManager = new StringJavaFileManage(standardJavaFileManager);
        // 构造源代码对象
        JavaFileObject javaFileObject = new StringJavaFileObject(fullClassName,sourceCode);
        // 获取一个编译任务
        JavaCompiler.CompilationTask task = compiler.getTask(null,javaFileManager,diagnosticCollector,null,null,Arrays.asList(javaFileObject));
        // 执行编译
        Boolean call = task.call();
        stopWatch.stop();
        log.info("class {} 编译完成,完成时间为:{},耗时时间为:{}毫秒",fullClassName,stopWatch.getStopTime(),stopWatch.getTime(TimeUnit.MILLISECONDS));
        return call;
    }

    /**
     * 获取编译时的错误信息
     * @return
     */
    public String getCompilerMessage(){
        StringBuilder sb = new StringBuilder();
        List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticCollector.getDiagnostics();
        for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
            sb.append(diagnostic.toString()).append("\r\n");
        }
        return sb.toString();
    }

    /**
     *自定义一个字符串源码对象
     */
    private class StringJavaFileObject extends SimpleJavaFileObject{
        // 等待编译的源码字段
        private String sourceCode;
        // java 源代码=》 StringJavaFileObject 对象的时候调用
        public StringJavaFileObject(String fullClassName, String sourceCode) {
            super(URI.create("string:///"+fullClassName.replaceAll("\\.","/")+Kind.SOURCE.extension), Kind.SOURCE);
            this.sourceCode = sourceCode;
        }

        // 字符串源码调用该方法

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return sourceCode;
        }
    }

    /**
     * 自定义一个编译之后的字节码对象
     */
    private class ByteJavaFileObject extends SimpleJavaFileObject {
        // 存放编译后的字节码对象
        private ByteArrayOutputStream bos;

        public ByteJavaFileObject(String fullClassName, Kind kind) {
            super(URI.create("string:///" + fullClassName.replaceAll("\\.", "/") + Kind.SOURCE.extension), kind);
        }

        // StringJavaFileManage 编译之后的字节码输出会调用该方法(把字节码输出到bos)
        @Override
        public OutputStream openOutputStream() {
            bos = new ByteArrayOutputStream();
            return bos;
        }

        // 在类加载器加载的时候需要用到
        public byte[] getCompilerBytes(){
            return bos.toByteArray();
        }

    }

    /**
     *     自定义一个javafile manage 来控制编译之后的字节码输出位置
      */
    private class StringJavaFileManage extends ForwardingJavaFileManager{

        /**
         * Creates a new instance of ForwardingJavaFileManager.
         *
         * @param fileManager delegate to this file manager
         */
        protected StringJavaFileManage(JavaFileManager fileManager) {
            super(fileManager);
        }

        // 获取输出的文件对象
        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            ByteJavaFileObject javaFileObject = new ByteJavaFileObject(className,kind);
            javaFileObjectMap.put(className,javaFileObject);
            return javaFileObject;
        }
    }

    /**
     * 自定义类加载器，用来加载动态的字节码
     */
    private class StringClassLoader extends ClassLoader{
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            ByteJavaFileObject fileObject = javaFileObjectMap.get(name);
            if(fileObject != null){
                byte [] bytes = fileObject.getCompilerBytes();
                return defineClass(name,bytes,0,bytes.length);
            }
            try {
                return ClassLoader.getSystemClassLoader().loadClass(name);
            } catch (Exception e){
                return super.findClass(name);
            }
        }
    }


    /**
     * 动态执行方法
     * @param params
     * @return
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public DeanResponse<String> runMethod(Map<String,?> params) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        StringClassLoader classLoader = new StringClassLoader();
        Class<?> clazz = classLoader.findClass(fullClassName);
        // 强制转换为接口调用
        ComputerScriptService service = (ComputerScriptService) clazz.getDeclaredConstructor().newInstance();
        DeanResponse result = service.call(params);
        log.debug("方法调用完成,执行结果:{}",result);
        return result;

    }

    /**
     * 动态执行方法
     * @param fullClassName
     * @param methodName
     * @param params
     * @return
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    public DeanResponse<String> runMethod(String fullClassName,String methodName,Map<String,?> params) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        StringClassLoader classLoader = new StringClassLoader();
        Class<?> clazz = classLoader.findClass(fullClassName);
        // 调用构造方法
        Constructor<?> constructor = clazz.getConstructor();
        // 创建对象
        Object instance = constructor.newInstance();
        // 获取调用方法
        Method method = clazz.getMethod(methodName,Map.class);
        // 反射调用
        DeanResponse result = (DeanResponse) method.invoke(instance,params);
        log.debug("方法调用完成,执行结果:{}",result);
        return result;

    }

    public  DeanResponse dynamicExecute(String sourceCodeFilePath,Map<String,?> params){
        return dynamicExecute(sourceCodeFilePath,params,true);
    }

    public DeanResponse dynamicExecute(String sourceCodeFilePath,Map<String,?> params,boolean forceCompiler){
        Object obj = null;
        // 动态编译代码
        if(!forceCompiler){
            obj = javaFileObjectMap.get(fullClassName);
        }
        // class 对象为空时 编译对象
        if(obj == null){
            // 读取sourceCode 文件
            setSourceCodeFile(sourceCodeFilePath);
            boolean compilerRes = compiler();
            // 显示编译错误信息
            if(!compilerRes){
                String errMsg = getCompilerMessage();
                log.error(errMsg);
                DeanRuntimeException.throwException(996,"编译失败,失败信息:"+errMsg);
            }
        }
        // 编译成功之后执行方法
        DeanResponse result;
        try {
            result = runMethod(params);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            result = DeanResponse.error(996,e.getMessage());
        }
        return result;
    }

}
